#region license
# This file is part of Grawlix.
# 
# Grawlix is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# 
# Grawlix is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public
# License along with Grawlix.  If not, see
# <http://www.gnu.org/licenses/>.
#endregion

"""An execution environment representing an AppDomain."""

namespace Grawlix.Services
import Grawlix.Meta
import ConcurrentBoo
import System
import System.Reflection
import System.Security
import System.Security.Permissions
import System.Security.Policy

[Disposable(SharedCleanup: Unload)]
transient class AppDomainEnvironment(ExecutionEnvironment):
"""An execution environment which manages a child AppDomain."""
	
	def constructor(
		[required] name as string,
		[required] basePath as string,
		[required] searchPaths as string,
		trackParent as bool):
	"""
	Create the cache with only an execution directory.
	Param name: The friendly name to use for this AppDomain.
	Param basePath: The root appdomain directory.
	Param searchPaths: A colon-separated list of root
		directories to search for assemblies.
	Param trackParent: Whether to pass a reference to the parent domain.
	"""
		setup = CreateSetupData()
		setup.ApplicationName = name
		setup.ApplicationBase = basePath
		setup.PrivateBinPath = searchPaths
		#setup.ConfigurationFile = config.ConfigurationPath
		assembly = Assembly.GetExecutingAssembly()
		_appdomain = AppDomain.CreateDomain(
			name,
			AppDomain.CurrentDomain.Evidence,
			setup,
			FindPermissions(),
			BuildStrongAssemblyName(assembly))
		_appdomain.SetData("PARENT_APPDOMAIN", AppDomain.CurrentDomain)
		_appdomain.Load(assembly.FullName)
		_appdomain.DoCallBack(StartLog)
	
	private static def StartLog():
	"""Initializes logging."""
		log4net.Config.XmlConfigurator.ConfigureAndWatch(
			IO.FileInfo(
				IO.Path.Combine(
					AppDomain.CurrentDomain.BaseDirectory,
					"Properties/Grawlix.log4net")))
		log Info, "Log initialized in " + AppDomain.CurrentDomain
	
	private def Unload():
	"""Unloads the appdomain."""
		if _appdomain is not null:
			appdomain = _appdomain
			_appdomain = null
			Primitive.QueueUserWorkItem() do (state):
				try:
					AppDomain.Unload(appdomain)
				except exception:
					log Warn, "Error during appdomain unload.\n" + exception
	
	private static def CreateSetupData():
	"""
	Creates a basic set of setup data.
	Returns: A slightly configured AppDomainSetup object.
	"""
    	# Appdomain setup has a bunch of complicated settings.
		# I did my best, but many can be tweaked.
		setup = AppDomainSetup()
		# Some minor trust options
		setup.DisallowApplicationBaseProbing = false
		setup.DisallowBindingRedirects = true # no idea
		setup.DisallowCodeDownload = true
		# can change; thought it might complicate things
		setup.DisallowPublisherPolicy = true
		
		# Shadow copy only world bins
		#setup.ShadowCopyFiles = "true"
		#setup.ShadowCopyDirectories = ""
		#setup.CachePath = "cache"
		
		# Share maximally; can tweak this
		# Probably won't affect anything that's been shadow copied
		setup.LoaderOptimization = LoaderOptimization.MultiDomain
		return setup
	
	private static def FindPermissions():
	"""
	Figure out what permissions to grant.
	Returns: The PermissionSet of permissions to grant.
	"""
		# TODO: Restrict to required permissions
		applicationTrust = AppDomain.CurrentDomain.ApplicationTrust
		if applicationTrust is not null:
			return applicationTrust.DefaultGrantSet.PermissionSet
		return SecurityManager.ResolvePolicy(AppDomain.CurrentDomain.Evidence)
	
	private static def BuildStrongAssemblyName(assembly as Assembly):
	"""
	Build a strong name for the specified assembly.
	Param assembly: The assembly to build for.
	Returns: The strong name.
	"""
		name = assembly.GetName()
		publicKey = name.GetPublicKey()
		# Only fails if compiled incorrectly
		assert publicKey is not null and publicKey.Length > 0
		return StrongName(
			StrongNamePublicKeyBlob(publicKey),
			name.Name,
			name.Version)
	
	override def LoadAssembly([required] fullName as string):
	"""
	Load and return a new instance.
	Param fullName: The full name of the assembly to load.
	Returns: The specified assembly.
	"""
		return _appdomain.Load(fullName)
	
	override def CreateInstance(
		[required] instanceType as Type,
		[required] *arguments as (object)):
	"""
	Load and return a new instance.
	Param instanceType: The type of object to create.
	Param arguments: The arguments to pass to the constructor.
	Returns: The new instance.
	"""
		instance = Activator.CreateInstance(
			_appdomain,
			instanceType.Assembly.FullName,
			instanceType.FullName,
			false,
			BindingFlags.Default,# |
			#BindingFlags.Public |
			#BindingFlags.NonPublic,
			null, arguments, null, null, null).Unwrap()
		# Sanity check; should throw under these conditions
		assert instance is not null
		# Warning: GetType() can cause injections in some cases
		assert instance.GetType() == instanceType
		return instance
	
	override def ToString():
	"""
	Get a string representation of this object.
	Returns: A string representation of this object.
	"""
		return "AppDomainEnvironment ${_appdomain.FriendlyName}"
	
	private _appdomain as AppDomain
	"""The wrapped appdomain."""
	
